﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Concurrent;
using System.Threading;

namespace CSharpExperiments
{
	public static class Func
	{
		public static Func<A> Fix<A>(Func<A> lambda)
		{
			return lambda;
		}
		
		public static Func<A, B> Fix<A, B>(Func<A, B> lambda)
		{
			return lambda;
		}
		
		public static Func<A, B, C> Fix<A, B, C>(Func<A, B, C> lambda)
		{
			return lambda;
		}
		
		public static Func<A, B, C, D> Fix<A, B, C, D>(Func<A, B, C, D> lambda)
		{
			return lambda;
		}
		
		public static Func<A, B, C, D, E> Fix<A, B, C, D, E>(Func<A, B, C, D, E> lambda)
		{
			return lambda;
		}

		public static Func<A, C> Compose<A, B, C>(this Func<B, C> outer, Func<A, B> inner)
		{
			return a => outer(inner(a));
		}

		public static Func<A, Func<B, C>> Curry<A, B, C>(this Func<A, B, C> func)
		{
			return a => b => func(a, b);
		}

		public static Func<A, Func<B, Func<C, D>>> Curry<A, B, C, D>(this Func<A, B, C, D> func)
		{
			return a => b => c => func(a, b, c);
		}

		public static Func<A, Func<B, Func<C, Func<D, E>>>> Curry<A, B, C, D, E>(this Func<A, B, C, D, E> func)
		{
			return a => b => c => d => func(a, b, c, d);
		}

		public static Func<A, Func<B, Func<C, Func<D, Func<E, F>>>>> Curry<A, B, C, D, E, F>(this Func<A, B, C, D, E, F> func)
		{
			return a => b => c => d => e => func(a, b, c, d, e);
		}

		public static Func<A, B, C> Uncurry<A, B, C>(this Func<A, Func<B, C>> func)
		{
			return (a, b) => func(a)(b);
		}

		public static Func<A, B, C, D> Uncurry<A, B, C, D>(this Func<A, Func<B, Func<C, D>>> func)
		{
			return (a, b, c) => func(a)(b)(c);
		}

		public static Func<A, B, C, D, E> Uncurry<A, B, C, D, E>(this Func<A, Func<B, Func<C, Func<D, E>>>> func)
		{
			return (a, b, c, d) => func(a)(b)(c)(d);
		}

		public static Func<A, B, C, D, E, F> Uncurry<A, B, C, D, E, F>(this Func<A, Func<B, Func<C, Func<D, Func<E, F>>>>> func)
		{
			return (a, b, c, d, e) => func(a)(b)(c)(d)(e);
		}
		
		public static Func<Z> ApplyPartial<A, Z>(this Func<A, Z> func, A a)
		{
			return () => func(a);
		}

		public static Func<B, Z> ApplyPartial<A, B, Z>(this Func<A, B, Z> func, A a)
		{
			return b => func(a, b);
		}

		public static Func<B, C, Z> ApplyPartial<A, B, C, Z>(this Func<A, B, C, Z> func, A a)
		{
			return (b, c) => func(a, b, c);
		}

		public static Func<B, C, D, Z> ApplyPartial<A, B, C, D, Z>(this Func<A, B, C, D, Z> func, A a)
		{
			return (b, c, d) => func(a, b, c, d);
		}

		public static Func<B, C, D, E, Z> ApplyPartial<A, B, C, D, E, Z>(this Func<A, B, C, D, E, Z> func, A a)
		{
			return (b, c, d, e) => func(a, b, c, d, e);
		}

		public static Func<B, A, Z> Flip<A, B, Z>(this Func<A, B, Z> func)
		{
			return (b, a) => func(a, b);
		}

		public static Func<C, B, A, Z> Flip<A, B, C, Z>(this Func<A, B, C, Z> func)
		{
			return (c, b, a) => func(a, b, c);
		}

		public static Func<D, C, B, A, Z> Flip<A, B, C, D, Z>(this Func<A, B, C, D, Z> func)
		{
			return (d, c, b, a) => func(a, b, c, d);
		}

		public static Func<E, D, C, B, A, Z> Flip<A, B, C, D, E, Z>(this Func<A, B, C, D, E, Z> func)
		{
			return (e, d, c, b, a) => func(a, b, c, d, e);
		}

		public static Func<B, A, Z> RotateRight<A, B, Z>(this Func<A, B, Z> func)
		{
			return Flip(func);
		}

		public static Func<C, A, B, Z> RotateRight<A, B, C, Z>(this Func<A, B, C, Z> func)
		{
			return (c, a, b) => func(a, b, c);
		}

		public static Func<B, A, Z> RotateLeft<A, B, Z>(this Func<A, B, Z> func)
		{
			return Flip(func);
		}

		public static Func<B, C, A, Z> RotateLeft<A, B, C, Z>(this Func<A, B, C, Z> func)
		{
			return (b, c, a) => func(a, b, c);
		}

		public static Func<A, Func<Z>> LazyResult<A, Z>(this Func<A, Z> func)
		{
			return a => () => func(a);
		}

		public static Func<A, B, Func<Z>> LazyResult<A, B, Z>(this Func<A, B, Z> func)
		{
			return (a, b) => () => func(a, b);
		}

		public static Func<A, B, C, Func<Z>> LazyResult<A, B, C, Z>(this Func<A, B, C, Z> func)
		{
			return (a, b, c) => () => func(a, b, c);
		}

		public static Func<A, B, C, D, Func<Z>> LazyResult<A, B, C, D, Z>(this Func<A, B, C, D, Z> func)
		{
			return (a, b, c, d) => () => func(a, b, c, d);
		}

		public static Func<A, B, C, D, E, Func<Z>> LazyResult<A, B, C, D, E, Z>(this Func<A, B, C, D, E, Z> func)
		{
			return (a, b, c, d, e) => () => func(a, b, c, d, e);
		}

		public static Func<Z> Memoize<Z>(this Func<Z> func)
		{
			Lazy<Z> lazy = new Lazy<Z>(func, LazyThreadSafetyMode.ExecutionAndPublication);
			return () => lazy.Value;
		}

		public static Func<A, Z> Memoize<A, Z>(this Func<A, Z> func)
		{
			var cache = new ConcurrentDictionary<A, Z>();
			return a => cache.GetOrAdd(a, func);
		}

		public static Func<A, B, Z> Memoize<A, B, Z>(this Func<A, B, Z> func)
		{
			var cache = new ConcurrentDictionary<Tuple<A, B>, Z>();
			Func<Tuple<A, B>, Z> f = t => cache.GetOrAdd(t, func.Tuplize());
			return f.Untuplize();
		}

		public static Func<A, B, C, Z> Memoize<A, B, C, Z>(this Func<A, B, C, Z> func)
		{
			var cache = new ConcurrentDictionary<Tuple<A, B, C>, Z>();
			Func<Tuple<A, B, C>, Z> f = t => cache.GetOrAdd(t, func.Tuplize());
			return f.Untuplize();
		}

		public static Func<A, B, C, D, Z> Memoize<A, B, C, D, Z>(this Func<A, B, C, D, Z> func)
		{
			var cache = new ConcurrentDictionary<Tuple<A, B, C, D>, Z>();
			Func<Tuple<A, B, C, D>, Z> f = t => cache.GetOrAdd(t, func.Tuplize());
			return f.Untuplize();
		}

		public static Func<A, B, C, D, E, Z> Memoize<A, B, C, D, E, Z>(this Func<A, B, C, D, E, Z> func)
		{
			var cache = new ConcurrentDictionary<Tuple<A, B, C, D, E>, Z>();
			Func<Tuple<A, B, C, D, E>, Z> f = t => cache.GetOrAdd(t, func.Tuplize());
			return f.Untuplize();
		}

        public static Func<A, B> Y<A, B>(Func<Func<A, B>, Func<A, B>> func)
        {
            var r = ((Recursive<A, B>)(g => func(x => g(g)(x))));
            return r(r);
        }

        delegate Func<A, B> Recursive<A, B>(Recursive<A, B> r);
	}
}
